/*
 * Copyright (C) 2020 Agrui
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ParameterCheck.h"
#include "OSMemory.h"
#include "IpcChannel.h"
#include "IpcChannelManager.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <errno.h>
#include <pthread.h>


#define SOCKET_IPC_PORT 8089
#define BUFFER_SIZE 4096
#define MAX_CLIENTS 5


typedef struct {
    IpcChannel channel;
    int serverFd;
    int clientFd;
    struct sockaddr_in address;
    pthread_t threadId;
} SocketServerChannel;

static void *socketWorker(void *parameter) {
    SocketServerChannel* channel = (SocketServerChannel*)parameter;
    int clientFds[MAX_CLIENTS];
    char buffer[BUFFER_SIZE];
    int dateLen = 0;
    int newSocket;
    fd_set readfds;
    int maxSd;
    int sd;
    int i;
    int activity;
    int addrlen = sizeof(channel->address);

    memset(clientFds, 0, sizeof(clientFds));

    while(TRUE) {
        FD_ZERO(&readfds);
        FD_SET(channel->serverFd, &readfds);
        maxSd = channel->serverFd;
        for (i = 0; i < MAX_CLIENTS; i++) {
            sd = clientFds[i];
            if (sd > 0) {
                FD_SET(sd, &readfds);
            }
            if (sd > maxSd) {
                maxSd = sd;
            }
        }

        activity = select(maxSd + 1, &readfds, NULL, NULL, NULL);
        if ((activity < 0) && (errno != EINTR)) {
            perror("select error");
        }

        for (i = 0; i < MAX_CLIENTS; i++) {
            sd = clientFds[i];
            if (sd <0 || !FD_ISSET(sd, &readfds)) {
                continue;
            }

            dateLen = read(sd, buffer, sizeof(buffer));
            if (dateLen == 0) {
                close(clientFds[i]);
                clientFds[i] = 0;
                channel->clientFd = 0;
                if (channel->channel.stateChangedCallback) {
                    channel->channel.stateChangedCallback(channel->channel.stateCallbackData, channel->channel.channelId, CHANNEL_STATE_DISCONNECTED);
                }
                continue;
            }

            if (dateLen < 0) {
                perror("read");
                break;
            }
            onIpcChannelDataReceive(channel->channel.channelId, buffer, dateLen);
        }


        if (FD_ISSET(channel->serverFd, &readfds)) {
            if ((newSocket = accept(channel->serverFd, (struct sockaddr *)&channel->address, (socklen_t*)&addrlen)) < 0) {
                perror("accept");
                continue;
            }
            for (i = 0; i < MAX_CLIENTS; i++) {
                if (clientFds[i] == 0) {
                    clientFds[i] = newSocket;
                    channel->clientFd = newSocket;
                    if (channel->channel.stateChangedCallback) {
                        channel->channel.stateChangedCallback(channel->channel.stateCallbackData, channel->channel.channelId, CHANNEL_STATE_CONNECTED);
                    }
                    break;
                }
            }
        }


    };

}


static void socketServerInit(SocketServerChannel* channel) {
    int opt = 1;
    if ((channel->serverFd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        return;
    }

    if (setsockopt(channel->serverFd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        return;
    }

    channel->address.sin_family = AF_INET;
    channel->address.sin_addr.s_addr = INADDR_ANY;
    channel->address.sin_port = htons(SOCKET_IPC_PORT);

    if (bind(channel->serverFd, (struct sockaddr *)&channel->address, sizeof(channel->address)) < 0) {
        return;
    }

    if (listen(channel->serverFd, 3) < 0) {
        close(channel->serverFd);
        return;
    }

    if (pthread_create(&channel->threadId, NULL, socketWorker, (void*)channel) != 0) {
        return;
    }

}

static void channelSendDataHandle(void* channelHandle, void* data, int dataLen) {
    SocketServerChannel* channel = (SocketServerChannel*)channelHandle;
    if (channel->clientFd > 0) {
        send(channel->clientFd, data, dataLen, 0);
    }
}

IpcChannel* newSocketServerChannel(int id) {
    SocketServerChannel* channel = OS_ALLOC_AND_RESET(SocketServerChannel);
    initIpcChannel(id, (IpcChannel*)channel);
    socketServerInit(channel);
    channel->channel.sendData = channelSendDataHandle;
    return (IpcChannel*)channel;
}

void deleteSocketServerChannel(IpcChannel* channel) {
    if (channel) {
        SocketServerChannel* socketChannel = (SocketServerChannel*)channel;
        if (socketChannel->serverFd > 0) {
            close(socketChannel->serverFd);
        }

        deleteIpcChannel(channel);
    }

}

